//
// Created by v on 19-7-2.
//

#include <arpa/inet.h>
#include <dirent.h>
#include <errno.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>

#include "CMCoapServer.h"

#define CPLOG(d, fmt, ...)                                                     \
    do                                                                         \
    {                                                                          \
        if (d)                                                                 \
        {                                                                      \
            printf("[%s:%d] ", __func__, __LINE__);                            \
            printf(fmt, ##__VA_ARGS__);                                        \
        }                                                                      \
    } while (0)

CMCoapServer::CMCoapServer(const char *addr, int port)
{
    m_addr   = addr;
    m_port   = port;
    m_inited = false;
    m_debug  = false;
}

CMCoapServer::~CMCoapServer() { deinit(); }

void CMCoapServer::startup()
{
#ifdef WITH_COAP
    coap_startup();
#endif
}

void CMCoapServer::cleanup()
{
#ifdef WITH_COAP
    coap_cleanup();
#endif
}

int CMCoapServer::init()
{
#ifdef WITH_COAP
    if (m_debug)
    {
        coap_dtls_set_log_level(LOG_DEBUG);
        coap_set_log_level(LOG_DEBUG);
    }
    int ret = initContext();
    if (ret < 0)
        return -1;

    m_inited = true;
#endif
    return 0;
}

int CMCoapServer::deinit()
{
#ifdef WITH_COAP
    if (m_inited)
    {
        coap_free_context(m_ctx);
    }
    m_inited = false;
#endif
    return 0;
}

int CMCoapServer::initContext()
{
#ifdef WITH_COAP
    coap_context_t * ctx = NULL;
    int              s;
    struct addrinfo  hints;
    struct addrinfo *result, *rp;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family   = AF_UNSPEC;  /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_DGRAM; /* Coap uses UDP */
    hints.ai_flags    = AI_PASSIVE | AI_NUMERICHOST;

    char s_port[16] = {0};
    sprintf(s_port, "%d", m_port);
    s = getaddrinfo(m_addr.c_str(), s_port, &hints, &result);
    if (s != 0)
    {
        fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
        return -1;
    }

    /* iterate through results until success */
    for (rp = result; rp != NULL; rp = rp->ai_next)
    {
        coap_address_t addr;

        if (rp->ai_addrlen <= sizeof(addr.addr))
        {
            coap_address_init(&addr);
            addr.size = rp->ai_addrlen;
            memcpy(&addr.addr, rp->ai_addr, rp->ai_addrlen);

            ctx = coap_new_context(&addr);
            if (ctx)
            {
                /* TODO: output address:port for successful binding */
                goto finish;
            }
        }
    }

    fprintf(stderr, "no context available for interface '%s'\n",
            m_addr.c_str());

finish:
    m_ctx = ctx;
    coap_set_app_data(m_ctx, this);
    freeaddrinfo(result);
#endif
    return 0;
}

#ifdef WITH_COAP
static void hnd_post_qlink_rd(coap_context_t *        ctx,
                              struct coap_resource_t *resource,
                              coap_session_t *session, coap_pdu_t *request,
                              coap_binary_t *token, coap_string_t *query,
                              coap_pdu_t *response)
{

    CMCoapServer *handler = (CMCoapServer *)coap_get_app_data(ctx);
    CPLOG(handler->getDebug(), "callback\n");
    coap_str_const_t *the_url = resource->uri_path;
    if (!the_url)
    {
        CPLOG(handler->getDebug(), "not find url\n");
        return;
    }
    int         ret      = -1;
    std::string respData = "";
    if (handler->m_callback && request)
    {
        size_t   dlen  = 0;
        uint8_t *ddata = NULL;
        ret            = coap_get_data(request, &dlen, &ddata);
        CPLOG(handler->getDebug(),
              "response with url [%s], ret [%d] data [%u][%s]\n", the_url->s,
              ret, dlen, ddata != NULL ? (const char *)ddata : "none");
        if (ret > 0)
        {
            char *t_str = (char *)valloc(dlen + 1);
            strncpy(t_str, (char *)ddata, dlen);
            t_str[dlen] = '\0';
            std::string d(t_str);
            ret = handler->m_callback->onRecvData((const char *)the_url->s, d,
                                                  respData);
        }
    }

    CPLOG(handler->getDebug(), "response callback done with ret [%d]\n", ret);
    if (ret == 0)
    {
        response->code = COAP_RESPONSE_200;
    }
    else
    {
        response->code = COAP_RESPONSE_400;
    }
    coap_add_data(response, respData.length(), (uint8_t *)respData.c_str());
}
#endif

int CMCoapServer::addResource(const char *url, int method)
{
    if (!m_inited)
        return -1;
#ifdef WITH_COAP
    coap_resource_t * r;
    coap_str_const_t *str =
        coap_new_str_const((const uint8_t *)url, strlen(url));
    r = coap_resource_init(str, 0);
    coap_register_handler(r, method == 2 ? COAP_REQUEST_POST : COAP_REQUEST_GET,
                          hnd_post_qlink_rd);

    coap_add_resource(m_ctx, r);
#endif
    return 0;
}

int CMCoapServer::run_once()
{
    if (!m_inited)
        return -11;
#ifdef WITH_COAP

    int ret = coap_run_once(m_ctx, 100);
#else
    int ret = 0;
#endif
    return ret;
}
